Skip to main content

Idempotency

What is idempotency

Idempotency is a principle in computing that ensures operations can be performed multiple times without changing the result beyond the initial application. In the context of web services, an idempotent HTTP method means that the result of a single successful request will be the same no matter how many times it is repeated.

Why is idempotency important

Idempotency is crucial for building reliable and predictable web services. It helps in handling duplicate requests safely, preventing unintended side effects or data corruption. This is especially important in distributed systems where network unreliability might cause clients to retry requests. Ensuring operations are idempotent helps in achieving robustness and resilience in such systems.

How to enforce idempotency in a route

First, ensure the following 2 modules are imported in your NestJS controller like such:

@Module({
controllers: [PaymentsController],
providers: [PaymentsService],
imports: [
// ...
// ...
// ...
OrganisationSecretsModule, // Used for API guard
IdempotencyModule, // Used for idempotency
],
exports: [PaymentsService],
})
export class PaymentsModule {}

Next, use the interceptor for all requests that are to be idempotent. The following is an example for the create payment route, in payments.controller.ts:

  @Post('/organisations/:organisation_id/payments/initialise')
@ApiOperation({
summary: 'Initialise a new payment',
})
@ApiOkResponse({
description: 'The payment has been successfully initialised.',
})
@UseInterceptors(IdempotentInterceptor) // Use the interceptor here
initialise(
@Param('organisation_id') organisationId: string,
@Body() initialisePaymentDto: InitialisePaymentDto,
) {
return this.paymentsService.initialise(
organisationId,
initialisePaymentDto,
);
}

How the interceptor works

The interceptor is a convenient middleware to ensure duplicate requests are not processed twice or more. Here's how it works:

  1. First, the interceptor checks if the request has a header Idempotency-Key. If it does, it checks if the request has been made before with the same key.
  2. If the request has been made before, it returns the same response as the previous request.
  3. If the request is new, it creates a new idempotency entry in the database and processes the request as usual.
  4. Upon successful or failed completion of the request, the interceptor updates the idempotency entry with the response and status code.
  5. The next time the same request is made with the same Idempotency-Key, the interceptor will return the stored response.

Please refer to the file backend/src/idempotency/idempotency.interceptor.ts for the implementation details.